package com.yonyou.pmclouds.reason.rmiimpl;

import com.alibaba.dubbo.config.annotation.Service;
import com.yonyou.pmclouds.ApplicationConsts;
import com.yonyou.pmclouds.basecom.bp.processor.AuditInfoInsertProcessor;
import com.yonyou.pmclouds.basecom.bp.processor.AuditInfoUpdateProcessor;
import com.yonyou.pmclouds.basecom.bp.template.SingleDeleteBpTemplate;
import com.yonyou.pmclouds.basecom.bp.template.SingleInsertBpTemplate;
import com.yonyou.pmclouds.basecom.bp.template.SingleUpdateBpTemplate;
import com.yonyou.pmclouds.basecom.consts.EnableStatusConst;
import com.yonyou.pmclouds.basecom.exception.BusinessException;
import com.yonyou.pmclouds.basecom.exception.BuziExCodeConst;
import com.yonyou.pmclouds.basecom.exception.ConcurrentOperationException;
import com.yonyou.pmclouds.basecom.lock.AutoReleaseLockUtil;
import com.yonyou.pmclouds.basecom.ref.itf.RefCodeConst;
import com.yonyou.pmclouds.basecom.ref.itf.ReferenceChecker;
import com.yonyou.pmclouds.basecom.runtime.RuntimeEnvironment;
import com.yonyou.pmclouds.basecom.util.CodeGenerateUtil;
import com.yonyou.pmclouds.basecom.util.CommonFieldConst;
import com.yonyou.pmclouds.reason.entity.ProblemReasonVO;
import com.yonyou.pmclouds.reason.mapper.ProblemReasonMapper;
import com.yonyou.pmclouds.reason.processor.*;
import com.yonyou.pmclouds.reason.rmiitf.ProblemReasonMaintain;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

/**
 * 问题原因档案维护
 * <br>
 * 由于出现引用校验，刻意调大超时时间，降低超时的可能性
 */
@Service(interfaceClass = ProblemReasonMaintain.class, version = ApplicationConsts.APPLICATION_VERSION)
public class ProblemReasonMaintainImpl implements ProblemReasonMaintain {
    public static final String LOCK_PREFIX = "problem_reason_";
    private static final String[] UPDATE_FIELDS = new String[]{CommonFieldConst.SNAME,CommonFieldConst.SCODE};
    @Autowired
    private ProblemReasonMapper mapper;
    @Autowired
    private ReferenceChecker referenceChecker;
    private ProblemReasonInsertRefCheck insertRefCheck = new ProblemReasonInsertRefCheck(this);
    private ProblemReasonDeleteRefCheck deleteRefCheck = new ProblemReasonDeleteRefCheck(this);
    /**
     * 单次导入的根节点最大数量
     */
    private static final int MAX_ROOT_NODE = 1000;

    @Override
    public String insert(ProblemReasonVO reasonVO) throws BusinessException {
        //  add lock
        if (!AutoReleaseLockUtil.addAutoReleaseLock(LOCK_PREFIX + reasonVO.getPkTenant())) {
            throw new ConcurrentOperationException();
        }
        // 校验上级编码、校验编码重复、校验级次
        SingleInsertBpTemplate<ProblemReasonVO> bp = new SingleInsertBpTemplate<>(mapper);
        bp.addBeforeProcessor(new ProblemReasonCodeCheck(mapper));
        bp.addBeforeProcessor(insertRefCheck);
        // 添加审计信息
        bp.addBeforeProcessor(new AuditInfoInsertProcessor<ProblemReasonVO>());
        reasonVO.setEnableStatus(EnableStatusConst.ENABLE);
        bp.insert(reasonVO);
        return reasonVO.getPkReason();
    }

    @Override
    public String update(ProblemReasonVO reasonVO) throws BusinessException {
        ProblemReasonVO oldReasonVO = mapper.getById(reasonVO.getPkReason());//获取原节点信息

        ProblemReasonVO[] problemReasonVOS =
                mapper.querySelfAndChildrenByCodeAndStatus(oldReasonVO.getScode(), oldReasonVO.getPkTenant(), EnableStatusConst.ALL);//获取所有子孙节点信息
        SingleUpdateBpTemplate<ProblemReasonVO> bp = new SingleUpdateBpTemplate<>(mapper, UPDATE_FIELDS);
        bp.addBeforeProcessor(new ProblemReasonUpNameAndCodeCheck(mapper));
        //审计信息
        bp.addBeforeProcessor(new AuditInfoUpdateProcessor<ProblemReasonVO>());

        reasonVO.setPkTenant(oldReasonVO.getPkTenant());
        bp.update(reasonVO);//修改父节点信息
        //修改子孙节点编码信息
        for (ProblemReasonVO problemReasonVO : problemReasonVOS) {
            if(problemReasonVO.getPkReason().equals(reasonVO.getPkReason())) continue;
            String scode = problemReasonVO.getScode();

            //替换原scode为新scode
            problemReasonVO.setScode(scode.replaceFirst(oldReasonVO.getScode(),reasonVO.getScode()));
            bp.update(problemReasonVO);
        }
        return reasonVO.getPkReason();
    }

    @Override
    public String delete(String reasonid, String changeFlag) throws BusinessException {
        //  add lock
        if (!AutoReleaseLockUtil.addAutoReleaseLock(LOCK_PREFIX + RuntimeEnvironment.getTenantId())) {
            throw new ConcurrentOperationException();
        }
        ProblemReasonVO vo = new ProblemReasonVO();
        vo.setPkReason(reasonid);
        vo.setChangeFlag(changeFlag);
        SingleDeleteBpTemplate<ProblemReasonVO> bp = new SingleDeleteBpTemplate<>(mapper);

        bp.addBeforeProcessor(deleteRefCheck);

        bp.delete(vo);
        return vo.getPkReason();
    }

    @Override
    public ProblemReasonVO enable(ProblemReasonVO reasonVo) throws BusinessException {
        //启用
        if(reasonVo.getPkParent() != null && !isParentEnable(reasonVo.getPkParent())){
            throw new BusinessException("所选数据的父节点为停用状态，请先启用父节点。");
        }
        reasonVo.setEnableStatus(EnableStatusConst.ENABLE);
        SingleUpdateBpTemplate<ProblemReasonVO> bp = new SingleUpdateBpTemplate<>(mapper, new String[]{EnableStatusConst.ENABLE_STATUS});
        bp.addBeforeProcessor(new AuditInfoUpdateProcessor<ProblemReasonVO>());
        bp.update(reasonVo);

        return reasonVo;
    }

    /**
     *    判断父节点是否是启用状态
     * @param pkParent
     * @return
     */
    public boolean isParentEnable(String pkParent){
        ProblemReasonVO reasonVo = mapper.getById(pkParent);
        if(reasonVo != null && reasonVo.getEnableStatus() == EnableStatusConst.ENABLE) return true;
        return false;
    }

    @Override
    public ProblemReasonVO disable(ProblemReasonVO reasonVo) throws BusinessException {
        //停用
        //把当前及子孙节点的状态置为停用
        SingleUpdateBpTemplate<ProblemReasonVO> bp = new SingleUpdateBpTemplate<>(mapper, new String[]{EnableStatusConst.ENABLE_STATUS});
        bp.addBeforeProcessor(new AuditInfoInsertProcessor<ProblemReasonVO>());
        return setDisable(reasonVo,bp);
    }

    /**
     *    递归进行数据的停用
     * @param reasonVo
     * @param bp
     * @return
     * @throws BusinessException
     */
    public ProblemReasonVO setDisable(ProblemReasonVO reasonVo, SingleUpdateBpTemplate bp) throws BusinessException {
        if(reasonVo.getChildReason() != null){
            ProblemReasonVO[] childReason = reasonVo.getChildReason();
            for (ProblemReasonVO problemReasonVO : childReason) {
                setDisable(problemReasonVO,bp);
            }
        }
        reasonVo.setEnableStatus(EnableStatusConst.DISABLE);
        bp.update(reasonVo);
        return reasonVo;
    }

    public int getProblemReasonRefStatus(String reasonid, String tenantid) throws BusinessException {
        // 子节点引用校验
        int childCount = mapper.countChild(reasonid);
        if (childCount > 0) {
            return STATUS_REF_BY_CHILD;
        }
        if (referenceChecker.checkReferenced(RefCodeConst.PROBLEM_REASON, new String[]{reasonid})) {
            return STATUS_REF_BY_BILL;
        }
        return STATUS_NOT_REF;
    }

    @Override
    public boolean referenceCheck(String reasonid) throws BusinessException {
        if (referenceChecker.checkReferenced(RefCodeConst.PROBLEM_REASON, new String[]{reasonid})) {
            return true;
        }
        return false;
    }

    @Override
    public boolean insertFromExcel(List<ProblemReasonVO> rootVOs, byte category) throws BusinessException {
        if (CollectionUtils.isEmpty(rootVOs))
            throw new BusinessException("问题原因为空，导入失败");

        if (rootVOs.size() > MAX_ROOT_NODE) {
            throw new BusinessException("导入一级问题原因过多，请按照一级问题原因拆分为多个EXCEL进行操作");
        }

        String pkTenant = RuntimeEnvironment.getTenantId();
        if (!AutoReleaseLockUtil.addAutoReleaseLock(LOCK_PREFIX + pkTenant))
            throw new ConcurrentOperationException();

        SingleInsertBpTemplate<ProblemReasonVO> bp = new SingleInsertBpTemplate<>(mapper);
        bp.addBeforeProcessor(new AuditInfoInsertProcessor<ProblemReasonVO>());
        bp.addBeforeProcessor(new ProbolemReasonMultiInsertBefore());
        for (int i = 0; i < rootVOs.size(); i++) {
            // 插入根节点及其子节点
            insertRootAndChildren(rootVOs.get(i), pkTenant, rootVOs.size(), i, category, bp);
        }

        return true;
    }

    /**
     * 插入根节点及其所有子节点
     *
     * @param rootVO   根节点
     * @param pkTenant 租户
     * @param nums     所有根节点的个数
     * @param position 当前根节点的位置
     * @param category 问题原因类别
     * @param bp       插入模板
     */
    private void insertRootAndChildren(ProblemReasonVO rootVO, String pkTenant, int nums, int position, byte category,
                                       SingleInsertBpTemplate<ProblemReasonVO> bp) throws BusinessException {
        // 校验根节点名称唯一性
        checkNameUnique(pkTenant, rootVO.getSname(), category);
        // 生成根节点编码
        String rootCode = generateScode(pkTenant, null, nums, position, category);
        rootVO.setScode(rootCode);
        rootVO.setPkTenant(pkTenant);
        rootVO.setEnableStatus(EnableStatusConst.ENABLE);
        rootVO.setCategory(category);
        // 插入根节点
        bp.insert(rootVO);

        // 队列，存储根节点的子节点
        Queue<ProblemReasonVO> queue = new LinkedList<>();
        queue.offer(rootVO);
        while (!queue.isEmpty()) {
            ProblemReasonVO currVO = queue.poll();
            // 插入子节点
            if (StringUtils.isEmpty(currVO.getPkReason()))
                bp.insert(currVO);

            ProblemReasonVO[] childVOs = currVO.getChildReason();
            if (ArrayUtils.isEmpty(childVOs)) continue;

            for (int j = 0; j < childVOs.length; j++) {
                ProblemReasonVO childVO = childVOs[j];
                checkNameUnique(pkTenant, childVO.getSname(), category);
                // 生成编码
                childVO.setScode(generateScode(pkTenant, currVO.getScode(), childVOs.length, j, category));
                childVO.setPkTenant(pkTenant);
                childVO.setEnableStatus(EnableStatusConst.ENABLE);
                childVO.setCategory(category);
                // 设置父节点、根节点
                childVO.setPkParent(currVO.getPkReason());
                childVO.setPkRoot(currVO.getPkRoot());
                queue.offer(childVO);
            }
        }
    }

    /**
     * 为当前节点生成唯一性编码
     *
     * @param pkTenant    租户
     * @param parentCode  父节点编码
     * @param brothersNum 同级节点的数量
     * @param position     当前节点在同级中位置
     * @param category 问题原因类别
     * @return
     */
    private String generateScode(String pkTenant, String parentCode, int brothersNum, int position,
                                 byte category) throws BusinessException {
        int width = Integer.toString(brothersNum).length();
        if (width < 2)
            width = 2;
        String codeFormat = "%0" + width + "d";
        // 不是根节点
        if (StringUtils.isNotEmpty(parentCode)) {
            return parentCode + String.format(codeFormat, position + 1);
        }
        // 根节点，要生成一个长度为6的唯一编码
        String sCodePrefix = CodeGenerateUtil.randomBase62Code(4);
        int retry = 0;
        int count = mapper.countCode(pkTenant, sCodePrefix, category);
        while (count > 0 && retry < 99) {//暂定最大尝试100次
            sCodePrefix = CodeGenerateUtil.randomBase62Code(4);
            count = mapper.countCode(pkTenant, sCodePrefix, category);
            retry++;
        }
        if (count > 0)
            throw new BusinessException("导入问题原因，生成编码出错，请稍后尝试。");

        return sCodePrefix + String.format(codeFormat, position + 1);
    }

    /**
     * 校验名称唯一性
     *
     * @param pkTenant
     * @param sname
     */
    private void checkNameUnique(String pkTenant, String sname, byte catetory) throws BusinessException {
        int count = mapper.countName(pkTenant, sname, catetory);
        if (count > 0)
            throw new BusinessException("【" + sname + "】已经存在相同的问题原因名称，请修改!", BuziExCodeConst.CODE_NAME_DUPLICATE);
    }

    public static final int STATUS_NOT_REF = 0;
    public static final int STATUS_REF_BY_CHILD = 1;
    public static final int STATUS_REF_BY_BILL = 2;


}
